﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System;
using System.Runtime.Remoting.Messaging;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

namespace Nova.UI
{
    /// <summary>
    /// The style of rendered text.
    /// </summary>
    [Flags]
    public enum TextStyle { Normal = 0, Bold = 1, Italic = 2, Proportional = 4, NoCache = 8 }

    /// <summary>
    /// Static helper methods for various WPF classes.
    /// </summary>
    public static class WPFUtil
    {
        #region /* STATIC HELPER METHODS */

        /// <summary>
        /// Process all pending UI events.
        /// </summary>
        public static void DoEvents()
        {
            Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { }));
        }

        /// <summary>
        /// Invoke the specified action on a worker thread, then invoke the completed action on the main thread
        /// when the background operation is complete.
        /// </summary>
        public static void ExecuteActionOnThread<T>(DispatcherObject dispatcherObject, Action<T> action, T actionParameter, Action completedAction)
        {
            action.BeginInvoke(actionParameter, ExecuteActionOnThreadCallback<T>, new AsyncState(dispatcherObject, completedAction));
        }

        private static void ExecuteActionOnThreadCallback<T>(IAsyncResult result)
        {
            // Clean up the asynchronous operation, and invoke the completion action on the main UI thread
            ((Action<T>)((AsyncResult)result).AsyncDelegate).EndInvoke(result);
            AsyncState asyncState = (AsyncState)result.AsyncState;
            asyncState.DispatcherObject.Dispatcher.Invoke(asyncState.Action);
        }

        private class AsyncState
        {
            public readonly DispatcherObject DispatcherObject;
            public readonly Action Action;
            public AsyncState(DispatcherObject dispatcherObject, Action action)
            {
                DispatcherObject = dispatcherObject;
                Action = action;
            }
        }

        /// <summary>
        /// Invoke the specified action on a worker thread, then invoke the completed action on the main thread
        /// when the background operation is complete.
        /// </summary>
        public static void ExecuteActionOnThread<T, U>(DispatcherObject dispatcherObject, Action<T> action, T actionParameter, Action<U> completedAction, U completedParameter)
        {
            action.BeginInvoke(actionParameter, ExecuteActionOnThreadCallback<T, U>, new AsyncState<U>(dispatcherObject, completedAction, completedParameter));
        }

        private static void ExecuteActionOnThreadCallback<T, U>(IAsyncResult result)
        {
            // Clean up the asynchronous operation, and invoke the completion action on the main UI thread
            ((Action<T>)((AsyncResult)result).AsyncDelegate).EndInvoke(result);
            AsyncState<U> asyncState = (AsyncState<U>)result.AsyncState;
            asyncState.DispatcherObject.Dispatcher.Invoke(asyncState.Action, asyncState.Parameter);
        }

        private class AsyncState<U>
        {
            public readonly DispatcherObject DispatcherObject;
            public readonly Action<U> Action;
            public readonly U Parameter;
            public AsyncState(DispatcherObject dispatcherObject, Action<U> action, U parameter)
            {
                DispatcherObject = dispatcherObject;
                Action = action;
                Parameter = parameter;
            }
        }

        /// <summary>
        /// Add a MenuItem to a Menu or ContextMenu.
        /// </summary>
        /// <param name="menu">The Menu or ContextMenu.</param>
        /// <param name="header">The description (header) of the MenuItem to be added.</param>
        /// <returns>The MenuItem added.</returns>
        public static MenuItem AddMenuItem(ItemsControl menu, string header)
        {
            MenuItem item = new MenuItem { Header = header };
            menu.Items.Add(item);
            return item;
        }

        /// <summary>
        /// Add a MenuItem to a Menu or ContextMenu.
        /// </summary>
        /// <param name="menu">The Menu or ContextMenu.</param>
        /// <param name="header">The description (header) of the MenuItem to be added.</param>
        /// <param name="clickHandler">The click event handler.</param>
        /// <param name="enabled">True if the menu item is enabled.</param>
        /// <returns>The MenuItem added.</returns>
        public static MenuItem AddMenuItem(ItemsControl menu, string header, RoutedEventHandler clickHandler, bool enabled)
        {
            MenuItem item = AddMenuItem(menu, header);
            item.IsEnabled = enabled;
            item.Click += clickHandler;
            return item;
        }

        /// <summary>
        /// Add a checkable MenuItem to a Menu or ContextMenu.
        /// </summary>
        /// <param name="menu">The Menu or ContextMenu.</param>
        /// <param name="header">The description (header) of the checkable MenuItem to be added.</param>
        /// <returns>The MenuItem added.</returns>
        public static MenuItem AddCheckableMenuItem(ItemsControl menu, string header)
        {
            MenuItem item = AddMenuItem(menu, header);
            item.IsCheckable = true;
            return item;
        }

        /// <summary>
        /// Add a command to a Menu or ContextMenu.
        /// </summary>
        /// <param name="menu">The Menu or ContextMenu.</param>
        /// <param name="command">The command to be added.</param>
        /// <param name="target">The target of the command.</param>
        /// <returns>The MenuItem added.</returns>
        public static MenuItem AddMenuItemCommand(ItemsControl menu, RoutedCommand command, IInputElement target)
        {
            MenuItem item = new MenuItem { Header = command.Name, Command = command, CommandTarget = target };
            menu.Items.Add(item);
            return item;
        }

        /// <summary>
        /// Add a command to a Menu or ContextMenu with a '...' button.
        /// </summary>
        /// <param name="menu">The Menu or ContextMenu.</param>
        /// <param name="header">The description (header) of the MenuItem to be added.</param>
        /// <param name="command">The command to be added.</param>
        /// <param name="target">The target of the command.</param>
        /// <returns>The MenuItem added.</returns>
        public static MenuItem AddMenuItemCommandWithButton(ItemsControl menu, string header, RoutedCommand command, IInputElement target)
        {
            WrapPanel wrapPanel = new WrapPanel();
            TextBlock textBlock = new TextBlock { Text = header + "   " };
            wrapPanel.Children.Add(textBlock);
            Button button = new Button { Content = " . . . ", Height = 18, Command = command, CommandTarget = target, CommandParameter = "..." };
            wrapPanel.Children.Add(button);
            MenuItem item = new MenuItem { Header = wrapPanel, Command = command, CommandTarget = target };
            menu.Items.Add(item);
            return item;
        }

        /// <summary>
        /// Add a checkable command to a Menu or ContextMenu.
        /// </summary>
        /// <param name="menu">The Menu or ContextMenu.</param>
        /// <param name="command">The command to be added.</param>
        /// <param name="target">The target of the command.</param>
        /// <param name="isChecked">The current checked state.</param>
        /// <returns>The MenuItem added.</returns>
        public static MenuItem AddCheckableMenuItemCommand(ItemsControl menu, RoutedCommand command, IInputElement target, bool isChecked)
        {
            MenuItem item = AddMenuItemCommand(menu, command, target);
            item.IsCheckable = true;
            item.IsChecked = isChecked;
            return item;
        }

        /// <summary>
        /// Add a Separator to a Menu or ContextMenu.
        /// </summary>
        /// <param name="menu">The Menu or ContextMenu.</param>
        public static void AddSeparator(ItemsControl menu)
        {
            menu.Items.Add(new Separator());
        }

        /// <summary>
        /// Add a command binding to a Window.
        /// </summary>
        /// <param name="window">The Window.</param>
        /// <param name="command">The command to be bound.</param>
        /// <param name="executed">The executed event handler for the command.</param>
        public static void AddCommandBinding(Window window, ICommand command, ExecutedRoutedEventHandler executed)
        {
            window.CommandBindings.Add(new CommandBinding(command, executed));
        }

        /// <summary>
        /// Add a command binding to a Window.
        /// </summary>
        /// <param name="window">The Window.</param>
        /// <param name="command">The command to be bound.</param>
        /// <param name="executed">The executed event handler for the command.</param>
        /// <param name="canExecute">The can-execute event handler for the command.</param>
        public static void AddCommandBinding(Window window, ICommand command, ExecutedRoutedEventHandler executed, CanExecuteRoutedEventHandler canExecute)
        {
            window.CommandBindings.Add(new CommandBinding(command, executed, canExecute));
        }

        /// <summary>
        /// Scroll the ListView so that the last item is visible.
        /// </summary>
        public static void ShowLastItem(ListView listView)
        {
            // Setup a call back, so we can scroll the item into view after the UI has updated
            listView.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action<ListView>(ShowLastListViewItem), listView);
        }

        private static void ShowLastListViewItem(ListView listView)
        {
            if (listView.Items.Count > 0)
                listView.ScrollIntoView(listView.Items[listView.Items.Count - 1]);
        }

        /// <summary>
        /// Scroll the control to the top, so the ScrollViewer doesn't retain its state.
        /// </summary>
        public static void ScrollToTop(ItemsControl itemsControl)
        {
            // Scroll the stupid ScrollViewer to the top so it doesn't retain its state if the
            // collection is cleared or otherwise updated (WPF bug!?).
            // Note that this code is dependent upon the specific control template in use!
            itemsControl.UpdateLayout();
            if (VisualTreeHelper.GetChildrenCount(itemsControl) > 0)
            {
                DependencyObject outer = VisualTreeHelper.GetChild(itemsControl, 0);
                if (outer != null)  // Might be a Border or a ListBoxChrome
                {
                    ScrollViewer scrollViewer = VisualTreeHelper.GetChild(outer, 0) as ScrollViewer;
                    if (scrollViewer != null)
                        scrollViewer.ScrollToTop();
                }
            }
        }

        /// <summary>
        /// Clear the control's items, also resetting the ScrollViewer so it doesn't retain its state.
        /// </summary>
        public static void Clear(ItemsControl itemsControl)
        {
            ScrollToTop(itemsControl);
            itemsControl.Items.Clear();
        }

        #endregion
    }
}
